package ru.bytedog.oiot.counter.bot.service.dashboard;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import ru.bytedog.oiot.counter.bot.bean.ErrorCode;
import ru.bytedog.oiot.counter.bot.service.ConfigService;
import ru.bytedog.oiot.counter.bot.entity.BotUser;
import ru.bytedog.oiot.counter.bot.entity.UserOtp;
import ru.bytedog.oiot.counter.bot.exception.ServiceException;
import ru.bytedog.oiot.counter.bot.repository.UserOtpRepository;
import ru.bytedog.oiot.counter.bot.util.FormatterUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.random.RandomGenerator;

@Service
@Slf4j
public class UserOtpService {

    private static final RandomGenerator GENERATOR = RandomGenerator.of("L64X256MixRandom");
    private final UserOtpRepository repository;

    private final ConfigService configService;

    public UserOtpService(UserOtpRepository repository, ConfigService configService) {
        this.repository = repository;
        this.configService = configService;
    }

    public String createOtp(BotUser user) throws ServiceException {
        log.debug("Генерация кода авторизации для пользователя {}", user.getUsername());
        LocalDateTime currentTime = LocalDateTime.now();
        List<UserOtp> activeCodes = repository.getAllActiveCodes(currentTime, user);
        if (!activeCodes.isEmpty()) {
            LocalDateTime lastActualTime = activeCodes.get(0).getActualTime();
            // Если время актуальности последнего запроса в текущем дне, то отправляем только время, иначе и дату
            throw new ServiceException(ErrorCode.OTP_CODE_ACTIVE_EXIST, currentTime.getDayOfMonth() != lastActualTime.getDayOfMonth()
                    ? FormatterUtils.formatDateTime(lastActualTime)
                    : FormatterUtils.formatTime(lastActualTime));
        }
        UserOtp newOtp = repository.save(new UserOtp()
                .setUser(user)
                .setCode(getAuthCode(configService.getOtpLength()))
                .setCreateTime(currentTime)
                .setActualTime(currentTime.plusMinutes(configService.getActualOtpMinutes())));
        log.debug("Код для пользователя {} успешно сгенерирован", user.getUsername());
        return newOtp.getCode();
    }

    public void validateCode(String otp, BotUser user) throws ServiceException {
        log.debug("Проверка кода авторизации пользователя {}", user.getUsername());
        LocalDateTime currentTime = LocalDateTime.now();
        List<UserOtp> activeCodes = repository.getAllActiveCodes(currentTime, user);
        Optional<UserOtp> validCode = activeCodes.stream()
                .filter(userOtp -> userOtp.getCode().equals(otp) && userOtp.getUser().getChatId().equals(user.getChatId()))
                .findFirst();
        if (validCode.isEmpty()) {
            log.debug("Код авторизации пользователя {} не прошел проверку", user.getUsername());
            throw new ServiceException(ErrorCode.OTP_CODE_ERROR);
        }
        repository.save(validCode.get()
                .setSuccess(true));
        log.debug("Код авторизации пользователя {} успешно прошел проверку", user.getUsername());
    }

    protected int generateCode(int length) {
        return GENERATOR.nextInt(0, (int) Math.pow(10, length));
    }

    protected String formatCode(int code, int length) {
        return StringUtils.leftPad(String.valueOf(code), length, "0");
    }

    private String getAuthCode(int length) {
        return formatCode(generateCode(length), length);
    }
}
